﻿namespace SubhadraSolutions.Sharp.Utils.Collections.ObjectModel
{
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;

    using SubhadraSolutions.Sharp.Utils.Collections.Generic;
    using SubhadraSolutions.Sharp.Utils.Threading;
    using SubhadraSolutions.Sharp.Utils.Validation;

    public class DispatcherAwareObservableCollection<T> : ObservableCollection<T>
    {
        /// <summary>
        /// The dispatcher provider
        /// </summary>
        private readonly IDispatcherProvider dispatcherProvider;

        /// <summary>
        /// Occurs when [collection changed].
        /// </summary>
        private event NotifyCollectionChangedEventHandler collectionChanged;
        
        /// <summary>
        /// Occurs when [property changed].
        /// </summary>
        private event PropertyChangedEventHandler propertyChanged;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        protected override event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                IDispatcher dispatcher = null;
                if (this.dispatcherProvider != null)
                {
                    dispatcher = this.dispatcherProvider.GetDispatcher(value.Target);
                }

                if (dispatcher == null)
                {
                    this.propertyChanged += value;
                }
                else
                {
                    this.propertyChanged += (sender, e) => dispatcher.Dispatch(() => value(sender, e));
                }
            }

            remove
            {
                this.propertyChanged -= value;
            }
        }


        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public override event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add
            {
                IDispatcher dispatcher = null;
                if (this.dispatcherProvider != null)
                {
                    dispatcher = this.dispatcherProvider.GetDispatcher(value.Target);
                }

                if (dispatcher == null)
                {
                    this.collectionChanged += value;
                }
                else
                {
                    this.collectionChanged += (sender, e) => dispatcher.Dispatch(() => value(sender, e));
                }
            }

            remove
            {
                this.collectionChanged -= value;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DispatcherAwareListNotifyCollectionChangedAdapter{T}" /> class.
        /// </summary>
        /// <param name="dispatcherProvider">The dispatcher provider.</param>
        public DispatcherAwareObservableCollection(IDispatcherProvider dispatcherProvider)
        {
            Guard.ArgumentNotNull(dispatcherProvider, "dispatcherProvider");
            this.dispatcherProvider = dispatcherProvider;
        }

        /// <summary>
        /// Raises the <see cref="E:CollectionChanged" /> event.
        /// </summary>
        /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var evt = this.collectionChanged;
            if (evt != null)
            {
                using (base.BlockReentrancy())
                {
                    GeneralHelper.SafeInvoke(evt, this, e);
                }
            }
        }

        /// <summary>
        /// Raises the <see cref="E:PropertyChanged" /> event.
        /// </summary>
        /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var evt = this.propertyChanged;
            if (evt != null)
            {
                GeneralHelper.SafeInvoke(evt, this, e);
            }
        }
    }
}
